#include "mmu.h"
#include "i386.h"

unsigned int * pag_kern_libre;
unsigned int * pag_user_libre;

unsigned int * page_dir;
unsigned int * page_table_1;
	
void inicializar_mmu()
{
	pag_kern_libre = (unsigned int *) INICIO_PAGINAS_KERNEL;
	pag_user_libre = (unsigned int *) INICIO_PAGINAS_USUARIO;	
}


unsigned int * pagina_libre_kernel(){
	unsigned int * pag_libre = pag_kern_libre;
	// si hay mas paginas libres, 
	if (pag_libre != (unsigned int*) INICIO_PAGINAS_USUARIO)
		//avanza y devuelve la que estaba siendo apuntada
		pag_kern_libre += 0x400;
	
	// si no hay mas paginas libres (se llego al limite)
	else {
		// pag_kern_libre queda como esta y la funcion devuelve 0
		pag_libre = 0;
	}
	return pag_libre;
}

unsigned int * pagina_libre_usuario(){
	unsigned int * pag_libre = pag_user_libre;
	// si hay mas paginas libres, 	
	if (pag_libre != (unsigned int*) TAMANO_PAGINA_4MB)
		//avanza y devuelve la que estaba siendo apuntada
		pag_user_libre += 0x400;
	// si no hay mas paginas libres (se llego al limite)
	else {
		// pag_user_libre queda como esta y la funcion devuelve 0
		pag_libre = 0;
	}
	return pag_libre;
}

void inicializar_dir_kernel(){
	//inicializa Directorio de Paginas en la primer pagina libre
	page_dir = pagina_libre_kernel();
	
	unsigned int i;
	// entradas en 0, con atributos:  supervisor, r/w, no_presente
	for (i = 0; i<1024; i++){
		page_dir[i] = 0 | 2;
	}

	// inicializa la primera tabla de paginas
	// hasta 0x00400000 con identity mapping 
	page_table_1 = pagina_libre_kernel();
	
	int pag = 0;
		
	for (i = 0; i<1024; i++) {
		// atributos:  supervisor, r/w, presente
		page_table_1[i] = pag | 3;
		pag = pag + TAMANO_PAGINA;
	} // ---hasta acá ha mapeado 1024 * 4KB = 4MB
	
	// setea la primera tabla de paginas en la primera entrada del directorio
	// atributos:  supervisor, r/w, PRESENTE
	page_dir[0] = (unsigned int) page_table_1;
	page_dir[0] = page_dir[0] | 3;
	
}

/** Hasta acá es toda la preparacion de la estructura para la correcta paginacion **/

/** ....a activar paginacion en el kernel .....                                                  **/


void extender_dir_kernel(){
	unsigned int i;
	int address = TAMANO_PAGINA_4MB;
	
	for (i = 1; i<16; i++) {
		// atributos: 10000011=131  == pagina_4MB 00000 supervisor, r/w, PRESENTE
		page_dir[i] = address | 131;
		address = address + TAMANO_PAGINA_4MB;
	}
}

/** Hasta acá, se han mapeado 64MB   **/







/** Hay que mapear las direcciones 0x000000000 a 0x00100000   **/
/** Son 16 paginas de 4KB                                     **/ 

unsigned int *  inicializar_dir_usuario(){
	unsigned int * page_dir_user;
	unsigned int * page_table_user_1;		
	
	page_dir_user = pagina_libre_kernel();
		
	page_table_user_1 = pagina_libre_kernel();
	
	// inicializa en 0 todas las entradas del directorio de usuario
	unsigned int i;
	for (i = 0; i<1024; i++)
		page_dir_user[i] = 0;
	
	// inicializa las primeras 256 entradas de la tabla de paginas de usuario
	// con atributos de supervisor
	int pag = 0;	
	for (i = 0; i < 256; i++) {
		page_table_user_1[i] = pag | 3;
		pag = pag + TAMANO_PAGINA;
	}
	
	// las restantes no estan presentes
	for (; i<1024; i++) {
		page_table_user_1[i] = 0;
	}
	
	page_dir_user[0] = (unsigned int) page_table_user_1;
	page_dir_user[0] = page_dir_user[0] | 7;		//marca la primera PDE con atributos de usuario
	return page_dir_user;
}


// Modifica el directorio/tabla para que virtual este mapeado en fisica
void mapear_pagina(unsigned int virtual, unsigned int cr3, unsigned int fisica, unsigned int atributos){
	
	unsigned int indice_directorio = (virtual >> 22) & 0x03FF;
	unsigned int indice_tabla = (virtual >> 12) & 0x03FF;
	unsigned int i;
	
	unsigned int nueva_entrada_tabla = (fisica & 0xFFFFF000) | (atributos & 0x0FFF);

	// obtiene direccion fisica del directorio
	unsigned int * directorio = (unsigned int *)( cr3 & 0xFFFFF000);
	
	// accede a una entrada del directorio
	unsigned int entrada_directorio = directorio[indice_directorio];
	
	//si no esta presente crea una nueva tabla de paginas
	if (!(entrada_directorio & 0x00000001)) {
		unsigned int * nueva_tabla = pagina_libre_kernel();
		
		if (nueva_tabla) {
			for (i = 0; i < indice_tabla; i++) {
				nueva_tabla[i] = 0;
			}
			//---- mapea la direccion virtual
			nueva_tabla[i] = nueva_entrada_tabla;
		
			for (i = indice_tabla + 1 ; i < 1024; i++) {
				nueva_tabla[i] = 0;
			}			
		}
		directorio[indice_directorio] = (unsigned int) nueva_tabla | 7;// | (atributos & 0x0FFF) | 1;
	}
	// si esta presente en el directorio, sigue el puntero a la tabla
	else {
		
		// obtiene la direccion fisica de la tabla
		unsigned int * tabla = (unsigned int *) (entrada_directorio & 0xFFFFF000);
		// accede a la entrada correspondiente en la tabla
		//---- mapea la direccion virtual
		tabla[indice_tabla] = nueva_entrada_tabla;

	}
	
	/**  FLUSH  TLB  **/
	tlbflush();
	
}

void unmapear_pagina(unsigned int virtual, unsigned int cr3){
	unsigned int indice_directorio = virtual >> 22;
	unsigned int indice_tabla = (virtual >> 12) & 0x03FF;
	
	// obtiene direccion fisica del directorio
	unsigned int * directorio = (unsigned int *)( cr3 & 0xFFFFF000);
	
	// accede a una entrada del directorio
	unsigned int entrada_directorio = directorio[indice_directorio];
	
	//si esta presente....
	if (entrada_directorio && 0x00000001) {
		// ...  obtiene la direccion fisica de la tabla
		unsigned int * tabla = (unsigned int *) (entrada_directorio & 0xFFFFF000);
		
		// accede a la entrada correspondiente en la tabla
		unsigned int entrada_tabla = tabla[indice_tabla];
		
		// ...si esta presente
		if (entrada_tabla && 0x00000001) {
			//---- unmapea la direccion virtual
			tabla[indice_tabla] = 0x00000000;
		}
		
	}

}
unsigned int obtener_virtual_libre(unsigned int * cr3) {

	unsigned int libre = 0;
	
	unsigned int salir = 0;
	unsigned int idir = 0;
	unsigned int itab = 256;
	unsigned int * tabla;

	while ( idir < 1024 && !salir)
	{  
		// entrada de directorio, NO presente
		if (!(cr3[idir] & 0x00000001))
		{
			libre = (idir << 22) & 0xFFC00000;
			salir = 1;
		}
		
		//entrada de directorio, con bit presente
		else 
		{
			
		tabla = (unsigned int *) (cr3[idir] & 0xFFFFF000);
			while (!salir && itab < 1024)
			{ 
				// entrada de tabla, bit no presente
				if (!(tabla[itab] & 0x00000001)) 
				{ 
					libre = ((idir << 22) | (itab<< 12)) & 0xFFFFF000;
					salir = 1;				
				}
				// entrada de tabla, bit presente
				else itab++;
			}
			itab = 0;
			idir++;
		}
	}
	return libre;	
}
